<?php

namespace App\Services;

use App\Constants\CollectTypeConstant;
use App\Constants\ContentReCommendConstant;
use App\Constants\CourseOrderStatusConstant;
use App\Constants\MiniProgramConstant;
use App\Constants\PayTypeConstant;
use App\Constants\RefundStatusConstant;
use App\Exceptions\BusinessException;
use App\Models\Collect;
use App\Models\Course;
use App\Models\CourseClass;
use App\Models\CourseComment;
use App\Models\CourseOrder;
use App\Models\CourseVideo;
use App\Models\CourseVideoHistory;
use App\Models\Member;
use App\Models\PayLog;
use App\Models\RefundLog;
use Illuminate\Contracts\Cache\LockTimeoutException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class CourService
{
    public function classList()
    {
        return CourseClass::where('status', 1)->where('pid', 0)->orderByDesc('sort')->orderByDesc('id')->get();
    }

    public function list(string $miniType, ?int $classId = null, ?int $limit = null)
    {
        $query = Course::where('status', 1);

        if (!$classId) {
            $settings = getMiniSettings($miniType, ContentReCommendConstant::GROUP);
            $day = $settings[ContentReCommendConstant::COURSE_TJ_DAY] ?? 30;

            $query->where('is_recommend', 1);
            $query->whereDate('created_at', '>=', now()->subDays($day)->format('Y-m-d'));
        } else {
            $query->where('class_id_1', $classId);
        }

        $query->orderByDesc('sort')->orderByDesc('id');

        if (!$limit) {
            $list = $query->paginate();
        } else {
            $list = $query->limit($limit)->get();
        }

        if (!$classId && !$list->count()) {
            $list = Course::where('status', 1)
                ->orderByDesc('sort')
                ->orderByDesc('id')
                ->when(!is_null($limit),
                    function ($q) use ($limit) {
                        return $q->limit($limit)->get();
                    },
                    function ($q) {
                        return $q->paginate();
                    }
                );
        }

        return $list;
    }

    public function details(int $id, int $memberId = 0)
    {
        $info = Course::find($id);

        if (!$info || !$info->status) throw new BusinessException('信息不存在');

        $info->load([
            'class1',
            'class2',
            'video'   => function ($q) {
                return $q->orderByDesc('sort');
            },
            'video.source',
            'comment' => function ($q) use ($memberId) {
                return $memberId > 0 ? $q->whereRaw('IF(member_id = ?, status <= ?, status = ?)', [$memberId, 2, 1]) : $q->where('status', 1);
            },
            'comment.member'
        ]);

        $showPay = $info->is_pay == 1;

        if ($memberId) {
            $memberInfo = [
                'member_id' => $memberId,
                'is_vip'    => Member::where('id', $memberId)->value('is_vip') ?: 0,
                'is_buy'    => CourseOrder::where('course_id', $id)
                    ->where('member_id', $memberId)
                    ->whereIn('status', [CourseOrderStatusConstant::IS_PAY, CourseOrderStatusConstant::REFUNDING, CourseOrderStatusConstant::IS_REFUND])
                    ->exists(),
            ];

            if ($showPay) {
                if ($memberInfo['is_buy']) {
                    $showPay = false;
                } else {
                    $showPay = !(!$info->vip_is_pay && $memberInfo['is_vip']);
                }
            }

            $info->member_info = $memberInfo;

            $info->is_collect = Collect::where([
                'member_id'   => $memberId,
                'belong_type' => CollectTypeConstant::TYPE_BY_COURSE,
                'belong_id'   => $id,
            ])->exists();

            $info->watch_history = CourseVideoHistory::where('member_id', $memberId)->where('course_id', $id)->get();
        }

        $info->show_pay = $showPay;

        return $info;
    }

    public function watchHistory(int $memberId, int $courseId, int $courseVideoId, int $pace = 0)
    {
        try {
            DB::beginTransaction();

            $course = Course::where('status', 1)->find($courseId);
            if (!$course) throw new BusinessException('课程不存在');

            $video = CourseVideo::find($courseVideoId);
            if (!$video) throw new BusinessException('课程视频不存在');

            $ret = CourseVideoHistory::updateOrCreate([
                'member_id'       => $memberId,
                'course_id'       => $courseId,
                'course_video_id' => $courseVideoId,
            ], ['pace' => $pace]);

            if ($ret->wasRecentlyCreated) {
                $course->increment('watch_num');
            }

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();
        }
    }

    public function collect(int $memberId, int $courseId)
    {
        $info = Course::find($courseId);
        if (!$info || !$info->status) throw new BusinessException('课程不存在');

        $info = Collect::where([
            'member_id'   => $memberId,
            'belong_type' => CollectTypeConstant::TYPE_BY_COURSE,
            'belong_id'   => $courseId,
        ])->first();

        if ($info) {
            $info->delete();
        } else {
            Collect::create([
                'member_id'   => $memberId,
                'belong_type' => CollectTypeConstant::TYPE_BY_COURSE,
                'belong_id'   => $courseId,
            ]);
        }
    }

    public function comment(int $memberId, int $courseId, string $content)
    {
        $info = Course::find($courseId);
        if (!$info || !$info->status) throw new BusinessException('课程不存在');

        CourseComment::create([
            'member_id' => $memberId,
            'course_id' => $courseId,
            'content'   => $content,
            'status'    => 0,
        ]);
    }

    public function getSource(int $courseId, int $courseVideoId, int $memberId)
    {
        $info = Course::find($courseId);
        if (!$info || !$info->status) throw new BusinessException('课程不存在');

        $video = CourseVideo::with(['source' => function ($q) {
            return $q->select(['id', 'name', 'cover', 'file'])->where('status', 1);
        }])->where('course_id', $courseId)->find($courseVideoId);
        if (!$video || !$video->source) throw new BusinessException('课程视频不存在');

        $history = CourseVideoHistory::where([
            'member_id'       => $memberId,
            'course_id'       => $courseId,
            'course_video_id' => $courseVideoId
        ])->first();

        if ($history) {
            $isWatch = true;
            $pace = $history->pace;
        }

        $video->source->course_video_id = $video->id;
        $video->source->is_watch = $isWatch ?? false;
        $video->source->pace = $pace ?? 0;

        $isVip = Member::where('id', $memberId)->value('is_vip') || 0;

        // 如果免费直接返回
        if (!$info->is_pay) return $video->source;

        // 如果已购买直接返回
        $exists = CourseOrder::where('member_id', $memberId)->where('course_id', $courseId)->whereIn('status', [CourseOrderStatusConstant::IS_PAY, CourseOrderStatusConstant::REFUNDING, CourseOrderStatusConstant::IS_REFUND])->exists();
        if ($exists) return $video->source;

        // 如果是vip免费且当前用户是vip直接返回
        if (!$info->vip_is_pay && $isVip) return $video->source;

        throw new BusinessException('当前课程未购买', 42000);
    }

    public function unifiedOrder(int $courseId, int $memberId, string $miniType)
    {
        $lock = Cache::lock('course_unified_order_' . $memberId, 10);

        try {
            DB::beginTransaction();

            $lock->block(5);

            $course = Course::find($courseId);
            if (!$course || !$course->status) throw new BusinessException('课程不存在');

            if (!$course->is_pay) throw new BusinessException('该课程无需付费');

            $exists = CourseOrder::where('member_id', $memberId)->where('course_id', $courseId)->whereIn('status', [CourseOrderStatusConstant::IS_PAY, CourseOrderStatusConstant::REFUNDING, CourseOrderStatusConstant::IS_REFUND])->exists();
            if ($exists) throw new BusinessException('该课程已购买');

            $member = Member::find($memberId);
            if (!$member) throw new BusinessException('用户不存在');

            $price = $course->pay_amount;
            if ($member->is_vip) {
                if (!$course->vip_is_pay) throw new BusinessException('vip用户该课程无需付费');

                $price = $course->vip_pay_amount;
            }

            if ($price <= 0) throw new BusinessException('购买金额不能小于0.01');

            $orderSn = generateUniqueSn();
            $paySn = generateUniqueSn();

            $payConfig = get_wechat_pay_config($miniType);
            $payConfig['notify_url'] = ngrokUrl('wechat_notify.course_pay', ['sn' => $paySn]);

            if (!$payConfig['app_id'] || !$payConfig['mch_id'] || !$payConfig['key']) throw new BusinessException('微信支付配置不能为空');

            CourseOrder::create([
                'member_id'        => $memberId,
                'mini_type'        => $miniType,
                'order_sn'         => $orderSn,
                'pay_sn'           => $paySn,
                'pay_amount'       => $price,
                'pay_time'         => 0,
                'status'           => CourseOrderStatusConstant::WAIT_PAY,
                'course_id'        => $courseId,
                'course_name'      => $course->name,
                'course_cover'     => $course->cover,
                'course_price'     => $course->pay_amount,
                'course_vip_price' => $course->vip_pay_amount,
            ]);

            PayLog::create([
                'mini_type'  => $miniType,
                'type'       => PayTypeConstant::TYPE_BY_BUY_COURSE,
                'pay_sn'     => $paySn,
                'pay_amount' => $price,
                'pay_type'   => 'wechat',
                'is_pay'     => false,
                'pay_time'   => 0,
                'extend'     => json_encode($payConfig)
            ]);

            $wechatPaymentService = new WechatPaymentService($payConfig);
            $ret = $wechatPaymentService->unifiedOrder([
                'body'         => '购买课程',
                'out_trade_no' => $paySn,
                'total_fee'    => $price,
                'openid'       => $member->open_id,
                'trade_type'   => 'JSAPI',
            ]);

            Log::debug(sprintf('支付单号【%s】微信支付响应结果', $paySn), $ret);

            if ($ret['return_code'] != 'SUCCESS') {
                throw new BusinessException($ret['return_msg'] ?? '获取微信配支付置信息失败');
            }

            if ($ret['result_code'] != 'SUCCESS') {
                throw new BusinessException($ret['err_code_des'] ?? '获取微信支付配置信息失败');
            }

            $result = $wechatPaymentService->getSdkPayConfig($ret['prepay_id']);
            if (!$result) {
                throw new BusinessException('获取微信支付配置信息失败');
            }

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            if (is_a($e, LockTimeoutException::class)) {
                throw new BusinessException('操作频繁,请稍后再试');
            } elseif (is_a($e, BusinessException::class)) {
                throw $e;
            } else {
                log_exception('获取微信小程序支付配置异常', $e);
                throw new BusinessException('获取微信小程序支付配置异常');
            }
        } finally {
            optional($lock)->release();
        }

        return $result;
    }

    public function memberOrder(int $memberId)
    {
        return CourseOrder::where('member_id', $memberId)
            ->whereIn('status', [CourseOrderStatusConstant::IS_PAY, CourseOrderStatusConstant::REFUNDING, CourseOrderStatusConstant::IS_REFUND])
            ->paginate();
    }

    public function collectList(int $memberId)
    {
        $list = Collect::with(['belong'])->where([
            'member_id'   => $memberId,
            'belong_type' => CollectTypeConstant::TYPE_BY_COURSE,
        ])->whereHas('belong')->paginate();

        if($list->count()) {
            $buyCourseIdArr = CourseOrder::where('member_id', $memberId)
                ->whereIn('course_id', $list->pluck('belong_id')->all())
                ->whereIn('status', [CourseOrderStatusConstant::IS_PAY, CourseOrderStatusConstant::REFUNDING, CourseOrderStatusConstant::IS_REFUND])
                ->pluck('course_id')
                ->all();
        }

        foreach ($list as $item) {
            $item->belong->is_buy = in_array($item->belong_id, $buyCourseIdArr ?? []);
        }

        return $list;
    }

    public function paidNotify(string $paySn)
    {
        $log = PayLog::where('pay_sn', $paySn)->first();
        if (!$log) {
            return response(<<<XML
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>
XML
                , 200, ['Content-Type' => 'text/xml']);
        }

        $config = json_decode($log->extend, true);
        $wechatPaymentService = new WechatPaymentService($config);

        return $wechatPaymentService->paidNotify($paySn, function ($paySn, $tradeNo) {
            return $this->paidHandel($paySn, $tradeNo);
        });
    }

    public function paidHandel(string $paySn, string $tradeNo): bool
    {
        try {
            DB::beginTransaction();

            $order = CourseOrder::where('pay_sn', $paySn)->first();
            if (!$order) throw new BusinessException(sprintf('支付单号【%s】 课程订单不存在', $paySn));
            if ($order->status != CourseOrderStatusConstant::WAIT_PAY) new BusinessException(sprintf('支付单号【%s】 课程订单状态不为待支付', $paySn));

            $payLog = PayLog::where('pay_sn', $paySn)->first();
            if (!$payLog) throw new BusinessException(sprintf('支付单号为【%s】 课程订单支付记录不存在', $paySn));

            // 更新订单状态
            $order->update([
                'status'   => CourseOrderStatusConstant::IS_PAY,
                'pay_time' => time(),
            ]);

            // 更新支付记录状态
            $payLog->update([
                'is_pay'      => true,
                'trade_no'    => $tradeNo,
                'notify_time' => time(),
                'pay_time'    => time(),
            ]);

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            if (is_a($e, BusinessException::class)) {
                Log::error($e->getMessage());
            } else {
                log_exception(sprintf('支付单号【%s】 订单更改状态异常', $paySn), $e);
            }

            return false;
        }

        return true;
    }

    public function refund(int $orderId): array
    {
        $order = CourseOrder::find($orderId);
        if (!$order) return resultFailedReturn('订单不存在');
        if ($order->status != CourseOrderStatusConstant::IS_PAY) return resultFailedReturn('该订单无法退款');

        $totalRefundAmount = RefundLog::where([
            'type'     => PayTypeConstant::TYPE_BY_BUY_COURSE,
            'order_id' => $orderId
        ])->where('refund_status', '<>', RefundStatusConstant::REFUND_FAILED)->sum('refund_amount') ?: 0;
        if ($totalRefundAmount >= $order->pay_amount) return resultFailedReturn("该订单已全部退款");

        $pay = PayLog::where(['type' => PayTypeConstant::TYPE_BY_BUY_COURSE, 'pay_sn' => $order->pay_sn])->first();
        if (!$pay) return resultFailedReturn('支付记录缺失');

        $orderAmount = $order->pay_amount;
        $refundAmount = bcsub($orderAmount, $totalRefundAmount);

        $refundNo = generateUniqueSn();

        RefundLog::create([
            'mini_type'       => $order->mini_type,
            'type'            => $pay->type,
            'order_id'        => $orderId,
            'order_sn'        => $order->order_sn,
            'pay_sn'          => $order->pay_sn,
            'pay_amount'      => $orderAmount,
            'pay_type'        => $pay->pay_type,
            'pay_trade_no'    => $pay->trade_no,
            'refund_no'       => $refundNo,
            'refund_amount'   => $refundAmount,
            'refund_status'   => RefundStatusConstant::REFUND_ING,
            'failed_msg'      => '',
            'refund_trade_no' => '',
        ]);

        $config = json_decode($pay->extend, true);

        if (isset($config['notify_url'])) {
            unset($config['notify_url']);
        }

        try {
            $wechatPaymentService = new WechatPaymentService($config);
            $ret = $wechatPaymentService->refundByTradeNo($pay->trade_no, $refundNo, $orderAmount, $refundAmount, [
                'notify_url' => ngrokUrl('wechat_notify.course_refund', ['sn' => $refundNo]),
            ]);

            if ($ret['return_code'] !== 'SUCCESS') {
                $this->updateRefundLog($refundNo, [
                    'refund_status' => RefundStatusConstant::REFUND_FAILED,
                    'failed_msg'    => $ret['err_code_des'] ?? '退款失败'
                ]);
                return resultFailedReturn($ret['return_msg'] ?? '退款失败');
            }

            if ($ret['result_code'] !== 'SUCCESS') {
                $this->updateRefundLog($refundNo, [
                    'refund_status' => RefundStatusConstant::REFUND_FAILED,
                    'failed_msg'    => $ret['err_code_des'] ?? '退款失败'
                ]);
                return resultFailedReturn($ret['err_code_des'] ?? '退款失败');
            }
        } catch (\Throwable $e) {
            log_exception('微信退款异常', $e);
            $this->updateRefundLog($refundNo, [
                'refund_status' => RefundStatusConstant::REFUND_FAILED,
                'failed_msg'    => $e->getMessage()
            ]);
            return resultFailedReturn('微信退款失败:' . $e->getMessage());
        }

        return resultSuccessReturn('订单退款申请成功');
    }

    public function refundNotify(string $refundNo)
    {
        $refund = RefundLog::where('refund_no', $refundNo)->first();
        if (!$refund) {
            return response(<<<XML
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>
XML
                , 200, ['Content-Type' => 'text/xml']);
        }

        $extend = PayLog::where('pay_sn', $refund->pay_sn)->value('extend');

        $config = json_decode($extend, true);

        if (isset($config['notify_url'])) {
            unset($config['notify_url']);
        }

        $wechatPaymentService = new WechatPaymentService($config);

        return $wechatPaymentService->refundNotify($refundNo, function ($refundNo, $tradeNo) {
            return $this->refundHandel($refundNo, $tradeNo);
        });
    }

    public function refundHandel(string $refundSn, string $tradeNo): bool
    {
        try {
            DB::beginTransaction();

            $log = RefundLog::where('refund_no', $refundSn)->first();
            if (!$log) throw new BusinessException(sprintf('退款单号【%s】 退款记录不存在', $refundSn));
            if ($log->status != RefundStatusConstant::REFUND_ING) throw new BusinessException(sprintf('退款单号【%s】 退款状态错误', $refundSn));

            $log->update([
                'refund_status'   => RefundStatusConstant::REFUND_SUCCESS,
                'refund_trade_no' => $tradeNo,
            ]);

            CourseOrder::where('id', $log->order_id)->update([
                'status' => CourseOrderStatusConstant::IS_REFUND
            ]);

            DB::commit();
        } catch (\Throwable $e) {
            DB::rollBack();

            if (is_a($e, BusinessException::class)) {
                Log::error($e->getMessage());
            } else {
                log_exception(sprintf('退款单号【%s】 退款成功 修改状态异常', $refundSn), $e);
            }

            return false;
        }

        return true;
    }

    private function updateRefundLog(string $refundNo, array $data)
    {
        RefundLog::where('refund_no', $refundNo)->update($data);
    }
}
